#pragma once

#include "vulkan_include.hpp"
#include <GLFW/glfw3.h>
#include <string>
#include <vector>
#include <memory>
#include "njm_device.hpp"

namespace njm {

    class NjmSwapChain
    {
    private:
        NjmDevice &device;
        VkExtent2D windowExtent;
        VkSwapchainKHR swapChain;
        std::shared_ptr<NjmSwapChain> oldSwapChain;
        VkFormat swapChainImageFormat;
        VkFormat swapChainDepthFormat;
        VkExtent2D swapChainExtent;

        VkRenderPass renderPass;
        std::vector<VkFramebuffer> swapChainFramebuffers;
       
        std::vector<VkImage> swapChainImages;
        std::vector<VkImageView> swapChainImageViews;
        std::vector<VkImage> depthImages;
        std::vector<VkDeviceMemory> depthImageMemorys;
        std::vector<VkImageView> depthImageViews;

        std::vector<VkSemaphore> imageAvailableSemaphores;
        std::vector<VkSemaphore> renderFinishedSemaphores;
        std::vector<VkFence> inFlightFences;
        std::vector<VkFence> imagesInFlight;
        size_t currentFrame = 0;

        // internal init
        void init();
        void createSwapChain();
        void createImageViews();
        void createDepthResources();
        void createRenderPass();
        void createFramebuffers();
        void createSyncObjects();

        // helper func
        VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector<VkSurfaceFormatKHR> &availableFormats);
        VkPresentModeKHR chooseSwapPresentMode(const std::vector<VkPresentModeKHR> &availablePresentModes);
        VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR &capabilities);
        VkFormat findSupportedFormat(const std::vector<VkFormat>& candidates, VkImageTiling tiling, VkFormatFeatureFlags features);

    public:
        static constexpr int MAX_FRAMES_IN_FLIGHT = 2;
        NjmSwapChain(NjmDevice &deviceRef, VkExtent2D windowExtent);
        NjmSwapChain(NjmDevice &deviceRef, VkExtent2D windowExtent, std::shared_ptr<NjmSwapChain> previous);
        ~NjmSwapChain();
        NjmSwapChain(const NjmSwapChain &) = delete;
        NjmSwapChain& operator=(const NjmSwapChain &) = delete;

        VkFramebuffer getFrameBuffer(int index) { return swapChainFramebuffers[index]; }
        VkRenderPass getRenderPass() { return renderPass; }
        VkImageView getImageView(int index) { return swapChainImageViews[index]; }
        size_t imageCount() { return swapChainImages.size(); }
        VkFormat getSwapChainImageFormat() { return swapChainImageFormat; }
        VkFormat getSwapChainDepthFormat() { return swapChainDepthFormat; }
        VkExtent2D getSwapChainExtent() { return swapChainExtent; }
        uint32_t width() { return swapChainExtent.width; }
        uint32_t height() { return swapChainExtent.height; }
        size_t getCurrentFrameIndex() { return currentFrame; }
        void setCurrentFrameIndex(size_t idx) { currentFrame = idx; }
        float extentAspectRatio() { return static_cast<float>(swapChainExtent.width) / static_cast<float>(swapChainExtent.height); }
        bool compareSwapFormats(const NjmSwapChain& otherSwapChain) const {
            return otherSwapChain.swapChainDepthFormat == swapChainDepthFormat && 
                    otherSwapChain.swapChainImageFormat == swapChainImageFormat; }
        // 
        VkFormat findDepthFormat();
        VkResult aquireNextImage(uint32_t *imageIndex);
        VkResult submitCommandBuffers(const VkCommandBuffer *buffers, uint32_t *imageIndex);
    };

}
